/*
 * XMPPClientHelper.java
 *
 * Created on March 23, 2007, 1:21 PM
 *
 * To change this template, choose Tools | Template Manager
 * and open the template in the editor.
 */

package org.xeerkat.restlet.xmpp;


import org.restlet.Client;

import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import java.util.UUID;
import java.util.logging.Level;
import org.infoset.xml.Document;
import org.infoset.xml.Element;
import org.infoset.xml.Item;
import org.infoset.xml.ItemConstructor;
import org.infoset.xml.ItemDestination;
import org.infoset.xml.XMLException;
import org.restlet.Context;
import org.restlet.Request;
import org.restlet.Response;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Method;
import org.restlet.data.Reference;
import org.restlet.data.Status;
import org.restlet.engine.ClientHelper;
import org.restlet.representation.StringRepresentation;
import org.xeerkat.restlet.xmpp.XMPP.ResponseListener;
import org.xeerkat.xmpp.ID;

/**
 *
 * @author alex
 */
public class XeerkatClientHelper  extends ClientHelper {
   Map<ID,Boolean> registered;
   Map<UUID,Object> waiting;
   Map<UUID,Response> result;
    /**
     * Constructor.
     * 
     * @param client
     *            The client to help.
     */
    public XeerkatClientHelper(Client client) {
        super(client);
        getProtocols().add(XMPP.XEERKAT_PROTOCOL);
        getProtocols().add(XMPP.XEERKAT_SYNC_PROTOCOL);
        registered = new TreeMap<ID,Boolean>();
        waiting = new TreeMap<UUID,Object>();
        result = new TreeMap<UUID,Response>();
    }

    
    protected XMPP.Identity getIdentity(boolean sync,ID sender)
    {
       Context context = getHelped().getContext();
       if (context==null) {
          getLogger().severe("Context is null for xeerkat protocol client!");
          return null;
       }
       XMPP xmpp = (XMPP)context.getAttributes().get(XMPP.XMPP_ATTR);
       XMPP.Identity identity = xmpp.getIdentity(sender);
       if (identity!=null && sync && registered.get(sender)==null) {
          synchronized (registered) {
             getLogger().info("Adding synchronized listener for sender "+sender);
             identity.addResponseListener(new ResponseListener() {
                public void onResponse(UUID id, Response response) {
                   result.put(id,response);
                   Object waiter = waiting.get(id);
                   if (waiter==null) {
                      return;
                   }
                   synchronized (waiter) {
                      waiter.notifyAll();
                   }
                }
                public void onOffline(ID id) {
                }
                public void onOnline(ID id) {
                   
                }
                public void onManagement(ID from,String authMethod,String username,String password,Document message)
                {
                }
             });
             registered.put(sender,Boolean.TRUE);
          }
       }
       return identity;
    }
    
    /**
     * Handles a call.
     * 
     * @param request
     *            The request to handle.
     * @param response
     *            The response to update.
     */
    public void handle(Request request, Response response) {
       Reference ref = request.getResourceRef();
       boolean sync = ref.getScheme().charAt(0)=='s';
       //System.out.println("Parsing: "+ref.toString());
       String sender = ref.getAuthority().replace("%40","@");
       String path = ref.getPath();
       getLogger().info("Path: "+path);
       if (path.startsWith("//")) {
          // no resource
          path = path.substring(2);
       } else {
          int secondSlash = path.indexOf('/',1);
          if (secondSlash<0) {
             response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
             response.setEntity(new StringRepresentation("No recipient was specified "+ref.getPath(),MediaType.TEXT_PLAIN));
             return;
          }
          sender = sender + path.substring(0,secondSlash);
          path = path.substring(secondSlash+1);
       }
       int slash = path.indexOf('/');
       if (slash<0) {
          response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
          response.setEntity(new StringRepresentation("No recipient was specified "+ref.getPath(),MediaType.TEXT_PLAIN));
          return;
       }
       String recipient = path.substring(0,slash).replace("%40","@");
       path = path.substring(slash);
       int secondSlash = path.indexOf('/',1);
       if (secondSlash<0) {
          recipient = recipient + path;
          path = "/";
       } else {
          recipient = recipient + path.substring(0,secondSlash);
          if (recipient.endsWith("/")) {
             recipient = recipient.substring(0,recipient.length()-1);
          }
          path = path.substring(secondSlash);
       }
       
       getLogger().info("sync="+sync+",sender="+sender+", recipient="+recipient+", path="+path);
       
       final XMPP.Identity identity = getIdentity(sync,new ID(sender));
       if (identity==null) {
          response.setStatus(Status.CLIENT_ERROR_BAD_REQUEST);
          response.setEntity(new StringRepresentation("No identity could be found for sender "+sender,MediaType.TEXT_PLAIN));
          return;
       }
       if (!identity.isConnected()) {
          response.setStatus(Status.CONNECTOR_ERROR_CONNECTION);
          response.setEntity(new StringRepresentation("The identity "+identity.getId()+" is not connected.",MediaType.TEXT_PLAIN));
          return;
       }
       if (request.getMethod().equals(Method.POST) ||
           request.getMethod().equals(Method.GET) ||
           request.getMethod().equals(Method.HEAD) ||
           request.getMethod().equals(Method.PUT) ||
           request.getMethod().equals(Method.DELETE)) {
          final String theReceiver = recipient;
          MessageSequence sequence = new MessageSequence(request,path) {
             public ItemDestination onMessage() 
                throws XMPPException
             {
                final ItemDestination stanza = identity.connection.getStanzaDestination();
                if (stanza==null) {
                   throw new XMPPException("Disconnected during send.");
                }
                
                // Wrap the messages in a XMPP message element
                return new ItemDestination() {
                   ItemConstructor constructor;
                   public void send(Item item) 
                     throws XMLException
                   {
                        switch (item.getType()) {
                           case DocumentItem:
                              stanza.send(item);
                              constructor = item.getInfoset().createItemConstructor();
                              Element messageE = constructor.createElement(org.xeerkat.xmpp.Protocol.MESSAGE);
                              messageE.setAttributeValue("from",identity.connection.getBindID().toString());
                              messageE.setAttributeValue("to",theReceiver);
                              getLogger().info("Sending message from "+identity.getId().toString()+" to "+theReceiver);
                              stanza.send(messageE);
                              break;
                           case DocumentEndItem:
                              stanza.send(constructor.createElementEnd(org.xeerkat.xmpp.Protocol.MESSAGE));
                              stanza.send(item);
                              break;
                           default:
                              stanza.send(item);
                        }
                   }
                };
             }
          };
          try {
             sequence.generate();
             if (sync) {
                
                UUID msgid = sequence.getId();
                waiting.put(msgid,msgid);
                synchronized (msgid) {
                   try {
                      msgid.wait(60*3*1000); // wait 3 minutes
                   } catch (InterruptedException ex) {

                   }
                }
                waiting.remove(msgid);
                Response remoteResponse = result.remove(msgid);
                if (remoteResponse==null) {
                   response.setStatus(Status.CLIENT_ERROR_REQUEST_TIMEOUT);
                } else {
                   getLogger().fine("Sync response received.");
                   response.setStatus(remoteResponse.getStatus());
                   Form headers = (Form)remoteResponse.getAttributes().get("org.restlet.http.headers");
                   if (headers!=null) {
                      response.getAttributes().put("org.restlet.http.headers",headers);
                   }
                   if (remoteResponse.isEntityAvailable()) {
                      getLogger().fine("Sync response has entity.");
                      response.setEntity(remoteResponse.getEntity());
                   }
                }
                
             } else {
                Form headers = new Form();
                response.getAttributes().put("org.restlet.http.headers",headers);
                headers.add("x-response-id",sequence.getId().toString());
                response.setStatus(Status.SUCCESS_OK);
             }
          } catch (IOException ex) {
             getLogger().log(Level.SEVERE,"Cannot read entity: "+ex.getMessage(),ex);
             response.setStatus(Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY);
             response.setEntity(new StringRepresentation("Cannot read entity: "+ex.getMessage(),MediaType.TEXT_PLAIN));
             checkForClose(identity,ex);
          } catch (XMLException ex) {
             getLogger().log(Level.SEVERE,"XML error sending request: "+ex.getMessage(),ex);
             response.setStatus(Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY);
             response.setEntity(new StringRepresentation("XML error sending request: "+ex.getMessage(),MediaType.TEXT_PLAIN));
             checkForClose(identity,ex);
          } catch (XMPPException ex) {
             getLogger().log(Level.SEVERE,"XMPP error sending request: "+ex.getMessage(),ex);
             response.setStatus(Status.CLIENT_ERROR_UNPROCESSABLE_ENTITY);
             response.setEntity(new StringRepresentation("XMPP error sending request: "+ex.getMessage(),MediaType.TEXT_PLAIN));
             checkForClose(identity,ex);
          }
       } else {
          response.setStatus(Status.CLIENT_ERROR_METHOD_NOT_ALLOWED);
          response.setEntity(new StringRepresentation("Unsupported method: "+request.getMethod(),MediaType.TEXT_PLAIN));
       }
    }
    
    void checkForClose(XMPP.Identity identity,Exception ex) {
       if (ex instanceof java.net.SocketException) {
          // we're closed;
          identity.failed();
       } else {
          Throwable t = ex.getCause();
          if (t instanceof Exception) {
             checkForClose(identity,(Exception)t);
          }
       }
    }

}
